package websocket

import (
	"github.com/gorilla/websocket"
	"sync"
	"time"
)

const (
	// Time allowed to write a message to the peer.
	writeWait = 10 * time.Second

	// Time allowed to read the next pong message from the peer.
	pongWait = 60 * time.Second

	// Send pings to peer with this period. Must be less than pongWait.
	pingPeriod = (pongWait * 9) / 10

	// Maximum message size allowed from peer.
	maxMessageSize = 2048
)

type connectionWs struct{
	fd uint64
	wsConn *websocket.Conn
	sendChan chan []byte
	closeChan chan struct{}		// 服务器主动关闭
	mq chan chanMessage			// 注入的消息接收

	mutex sync.Mutex  	// 对closeChan关闭上锁
	isClosed bool  		// 防止closeChan被关闭多次
}

// 初始化连接
func newConnection(wsConn *websocket.Conn, fd uint64, mq chan chanMessage) (connection *connectionWs) {
	connection = &connectionWs{
		fd : fd,
		wsConn: wsConn,
		sendChan: make(chan []byte, 100),
		closeChan: make(chan struct{}, 1),
		mq : mq,
	}

	// 启动读写协程
	go connection.readLoop()
	go connection.writeLoop()

	return
}

// Close 主动关闭
func (c *connectionWs) Close() {
	c.mutex.Lock()
	if !c.isClosed {
		c.wsConn.Close()

		// 通知上层网络关闭
		chanMes := chanMessage{
			Fd:   c.fd,
			messageType: chanMessageTypeNetBroken,
		}
		c.mq <- chanMes

		close(c.closeChan)
		c.isClosed = true
	}
	c.mutex.Unlock()
}

// WriteMessage 写入消息 放入channel中异步发送
func (c *connectionWs) WriteMessage(data []byte) {
	c.sendChan <- data
}

// 读循环
func (c *connectionWs) readLoop() {
	var (
		data []byte
		err error
	)

	c.wsConn.SetReadLimit(maxMessageSize)
	c.wsConn.SetReadDeadline(time.Now().Add(pongWait))
	c.wsConn.SetPongHandler(func(string) error { c.wsConn.SetReadDeadline(time.Now().Add(pongWait)); return nil })
	for {
		_, data, err = c.wsConn.ReadMessage()
		if err != nil {
			if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseAbnormalClosure) {
				LOG.Errorf("readLoop error: %v", err)
			}
			goto ERR
		}

		chanMes := chanMessage{
			Fd:   c.fd,
			Data: data,
		}

		select {
		case c.mq <- chanMes:
		case <- c.closeChan:
			goto ERR
		}
	}
ERR:
	c.Close()
}

// 写循环
func (c *connectionWs) writeLoop() {
	var (
		data []byte
		err error
		ticker = time.NewTicker(pingPeriod)
	)

	for {
		select {
		case data = <- c.sendChan:
			c.wsConn.SetWriteDeadline(time.Now().Add(writeWait))
			if err = c.wsConn.WriteMessage(websocket.TextMessage, data); err != nil {
				goto ERR
			}
		case <- c.closeChan:
			goto ERR
		case <-ticker.C:
			c.wsConn.SetWriteDeadline(time.Now().Add(writeWait))
			if err := c.wsConn.WriteMessage(websocket.PingMessage, nil); err != nil {
				goto ERR
			}
		}

	}
ERR:
	c.Close()
}